package com.ouroboroswiki.core.content.markup.processor;

import java.io.IOException;
import java.io.StringWriter;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import com.ouroboroswiki.core.Content;
import com.ouroboroswiki.core.ContentPath;
import com.ouroboroswiki.core.ContentRepository;
import com.ouroboroswiki.core.ContentUtil;
import com.ouroboroswiki.core.ContentValidationResult;
import com.ouroboroswiki.core.Version;
import com.ouroboroswiki.core.content.PlaceholderContent;

public class VelocityContentMarkupHandler extends AbstractExternalContentMarkupHandler implements MarkupHandler {
		
	private static final Logger log = Logger.getLogger( VelocityContentMarkupHandler.class.getName() );
	
	public static final String ELEMENT_VERSION 	= "version";

	public static final String ATTRIBUTE_URL = "_url";
	
	private String elementName;
	private String linkRoot;
	private Map<String, String> templates;
	
	private ContentRepository source;
	
	private VelocityEngine engine;
	private VelocityContext context;
	
	public VelocityContentMarkupHandler(
			VelocityEngine engine, 
			VelocityContext context,
			String elementName, 
			String linkRoot) {
		this.elementName = elementName;
		this.linkRoot = linkRoot;
		this.engine = engine;
		this.context = context;
	}
	
	public ContentRepository getSource() {
		return source;
	}

	public void setSource(ContentRepository source) {
		this.source = source;
	}
	
	public Map<String, String> getTemplates() {
		return templates;
	}

	public void setTemplates(Map<String, String> templates) {
		this.templates = templates;
	}

	@Override
	public List<Node> replace(Object principal, Element e, Document parent,
			Map<String, Content> childContent, ContentPath currentPath,
			Version version, Collection<ContentValidationResult> validations, 
			Map<String, Object> properties) throws IOException {
		Element linkElement = parent.createElement(elementName);
		ContentPath linkPath = buildPathFromContext(e, currentPath);
		
		Map<String, Object> ctxProperties = new HashMap<String, Object>();
		String path = linkRoot + toLinkPath(e, linkPath);
		ctxProperties.put(ATTRIBUTE_URL, path);
		// load any other attributes and child elements
		NamedNodeMap attributes = e.getAttributes();
		for( int i=0; i<attributes.getLength(); i++ ) {
			Attr attribute = (Attr)attributes.item(i);
			ctxProperties.put( attribute.getNodeName(), attribute.getNodeValue() );
		}
		VelocityContext ctx = new VelocityContext(ctxProperties, context);
		for( Map.Entry<String, String> entry : templates.entrySet() ) {
			String attributeName = entry.getKey();
			String template = entry.getValue();
			StringWriter attributeWriter = new StringWriter(template.length());
			engine.evaluate(ctx, attributeWriter, path, template);
			linkElement.setAttribute(attributeName, attributeWriter.toString());
		}
		
		NodeList childNodes = e.getChildNodes();
		for( int i=0; i<childNodes.getLength(); i++ ) {
			Node childNode = childNodes.item( i );
			linkElement.appendChild( childNode.cloneNode(true) );				
		}
		// look up the child content and add it
		String id = e.getAttribute(ATTRIBUTE_ID);
		if( id != null && id.length() > 0 ) {
			try {
				// populate with a placeholder content
				Content content = new PlaceholderContent(linkPath);
				childContent.put( id, content );
			} catch( Exception ex ) {
				validations.add(new ContentValidationResult(
						ContentValidationResult.Level.Error, 
						"unable to load child content "+id, 
						ex
				));
				log.log(Level.WARNING, "unable to load child content "+id, ex);
			}

		}
		
		return Arrays.asList((Node)linkElement);
	}
		
	protected String toLinkPath( Element e, ContentPath linkPath ) {
		return ContentUtil.toString(linkPath);
	}

}
